#include <Arduino.h>

#include <Arduino.h>
#include "base64.h"
#include <WiFi.h>
#include "HTTPClient.h"
#include "cJSON.h"
#include <I2S.h>
#include <ArduinoJson.h>
#include <SoftwareSerial.h>
#include "UTF8ToGB2312.h"
#define MYPORT_TX 1
#define MYPORT_RX 2

EspSoftwareSerial::UART myPort;

uint8_t voicedata[] = { 0xFD, 0x00, 0x06, 0x01, 0x01, 0x5B, 0x76, 0x31, 0x5D };  //voicedata[7] =  0x31 ~ 0x39
// #define data_len 16000
// #define key 4             //端口0
// #define ADC 2             //端口39
// #define led 15            //端口2

HTTPClient http_client;
// 1. Replace with your network credentials
// const char *ssid = "Doctor's_home";
// const char *password = "115115115";
const char *ssid = "IQOO";
const char *password = "12345678";
// 2. Check your Aduio port
const int buttonPin = 9;  // the number of the pushbutton pin
const int ledPin = 21;    // the number of the LED pin
hw_timer_t *timer = NULL;
const int adc_data_len = 8000 * 3;
const int data_json_len = adc_data_len * 3;
uint16_t *adc_data;
char *data_json;
// uint16_t adc_data[data_len];    //16000个数据，8K采样率，即2秒，录音时间为2秒，想要实现更长时间的语音识别，就要改这个数组大小
// char data_json[json_len];          //用于储存json格式的数据,大一点,JSON编码后数据字节数变成原来的4/3,所以得计算好,避免出现越界
//和下面data_json数组的大小，改大一些。
uint8_t adc_start_flag = 0;     //开始标志
uint8_t adc_complete_flag = 0;  //完成标志


// 3. Replace with your MiniMax API key
const char *apiKey = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiIyMzQ1dm9yIiwiVXNlck5hbWUiOiIyMzQ1dm9yIiwiQWNjb3VudCI6IiIsIlN1YmplY3RJRCI6IjE3NTk0ODIxODAxMDAxNzAyMDgiLCJQaG9uZSI6IjE1MDcyNjQxNTYxIiwiR3JvdXBJRCI6IjE3NTk0ODIxODAwOTU5NzU5MDQiLCJQYWdlTmFtZSI6IiIsIk1haWwiOiIiLCJDcmVhdGVUaW1lIjoiMjAyNC0xMC0xNCAyMjoyODo0NSIsImlzcyI6Im1pbmltYXgifQ.xcIqNlrjffSbx2JLnBmGvh7XHL0j941Vo3qYFo_zxWZD6yfVqCp4nU-AQP6zvXxZBK3G3ocjzUXgd0WpnUrYaUWwOVkpKUbhMBGgmmXxOg4N8vcWCkgN3LbQYkqD5_n7II0ubZjIe9SrWOds9T4m6NLgVZZhEMU0JpmL9w2S2JLu_Ce1yIom3zg7zo1_wopnIWoEdNwGLi9P_GqiwPnGo7WR3p6Qo1LJsYk9amyl4sZk2daYMP1BQG07IlDh4Po3cS7HbioAXMMH-I6iJ0evR227rEUi2A4_I8acgTsOTSj-5UMmUJ_aKTLMuS_t8qz_wzcu_m0mas_eWQVVb0-Ggg";
// 3. Replace with your baidu voice detect token
String token = "24.17ce85634b9bdfc0fc1c824f7768095f.2592000.1731509139.282335-57722200";
HTTPClient http;
String token_key = String("Bearer ") + apiKey;
// Send request to MiniMax API
String inputText = "你好，minimax！";
String apiUrl = "https://api.minimax.chat/v1/text/chatcompletion_v2";
int httpResponseCode;
String response, question, answer;
JsonDocument jsonDoc;

uint32_t num = 0;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR onTimer() {
  // Increment the counter and set the time of ISR
  portENTER_CRITICAL_ISR(&timerMux);
  if (adc_start_flag == 1) {
    //Serial.println("");
    // adc_data[num] = analogRead(ADC);
    adc_data[num] = I2S.read();
    num++;
    if (num >= adc_data_len) {
      adc_complete_flag = 1;
      adc_start_flag = 0;
      num = 0;
      //Serial.println(Complete_flag);
    }
  }
  portEXIT_CRITICAL_ISR(&timerMux);
}



void speech(String data) {

  //0--空闲  1--繁忙  2--异常
  // while (workstate() > 0) {
  //   if (workstate() == 1) Serial.println("tts busy");
  //   if (workstate() == 2) Serial.println("tts wrong");
  // }
  String utf8_str = data;
  String gb2312_str = GB.get(utf8_str);
  unsigned char head[gb2312_str.length() + 6];
  // 定义无符号字符类型数组，将 GB2312 编码的字符串复制到数组中
  unsigned char gb2312_data[gb2312_str.length() + 1];
  memset(gb2312_data, 0, sizeof(gb2312_data));
  strncpy((char *)gb2312_data, gb2312_str.c_str(), gb2312_str.length());
  // head byte
  head[0] = 0xFD;
  // length bytes
  unsigned int dat_len = gb2312_str.length() + 3;
  head[1] = dat_len >> 8;
  head[2] = dat_len;
  // cmd byte
  head[3] = 0x01;
  // para byte
  head[4] = 0x01;
  // send each character individually
  for (int i = 0; i < gb2312_str.length(); i++) {
    head[i + 5] = gb2312_data[i];
  }
  // 计算异或值并添加到数组，额这个是syn那个芯片的例程，这个异或计算有没有用我懒得改了。反正代码能跑。
  head[gb2312_str.length() + 5] = head[0];
  for (int i = 1; i < gb2312_str.length() + 5; i++) {
    head[gb2312_str.length() + 5] ^= head[i];
  }
  // 发送字符数组到串口
  for (int j = 0; j < gb2312_str.length() + 6; j++) {
    myPort.write(head[j]);
  }
  delay(gb2312_str.length() * 100);
  // Serial.println(data);
}
// 查询tts合成工作状态 返回1表示繁忙 0表示空闲
int workstate() {
  unsigned char head[4] = { 0xFD, 0x00, 0x01, 0x21 };

  // 发送字符数组到串口
  for (int j = 0; j < 4; j++) {
    myPort.write(head[j]);
  }

  // 等待myPort的返回
  while (myPort.available() < 1) {
    // 可以在这里加入一些延时，以防止过快地查询
    delay(150);
  }

  // 读取并处理返回的数据
  byte response = myPort.read();

  // 返回相应的状态值
  if (response == 0x4E) {
    // 繁忙
    return 1;
  } else if (response == 0x4F) {
    // 空闲
    return 0;
  } else {
    // 未知状态，可以根据需要进行处理
    return 2;
  }
}

String getGPTAnswer(String inputText) {
  http.begin(apiUrl);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", token_key);
  String payload = "{\"model\":\"abab5.5s-chat\",\"messages\":[{\"role\": \"system\",\"content\": \"你是鹏鹏的生活助手机器人，要求下面的回答严格控制在256字符以内。\"},{\"role\": \"user\",\"content\": \"" + inputText + "\"}]}";
  httpResponseCode = http.POST(payload);
  if (httpResponseCode == 200) {
    response = http.getString();
    http.end();
    Serial.println(response);
    // Parse JSON response
    deserializeJson(jsonDoc, response);
    String outputText = jsonDoc["choices"][0]["message"]["content"];
    return outputText;
    // Serial.println(outputText);
  } else {
    http.end();
    Serial.printf("Error %i \n", httpResponseCode);
    speech("语言大模型故障，请检查api是否失效");
    return "<error>";
  }
}

void setup() {

  //Serial.begin(921600);
  Serial.begin(115200);
  adc_data = (uint16_t *)ps_malloc(adc_data_len * sizeof(uint16_t));  //ps_malloc 指使用片外PSRAM内存
  if (!adc_data) {
    Serial.println("Failed to allocate memory for adc_data");
  }

  data_json = (char *)ps_malloc(data_json_len * sizeof(char));  // 根据需要调整大小
  if (!data_json) {
    Serial.println("Failed to allocate memory for data_json");
  }
  myPort.begin(115200, SWSERIAL_8N1, MYPORT_RX, MYPORT_TX, false);
  delay(1000);
  if (!myPort) {  // If the object did not initialize, then its configuration is invalid
    Serial.println("Invalid EspSoftwareSerial pin configuration, check config");
    while (1) {  // Don't continue with invalid configuration
      delay(1000);
    }
  }
  while(workstate() > 0){
    delay(1000);
  }
  speech("系统开机");
  delay(1500);
  for (int i = 0; i < sizeof(voicedata) / sizeof(voicedata[0]); i++) {
    myPort.write(voicedata[i]);
  }
  delay(1000);
  speech("系统音量以调小");
  // pinMode(ADC, ANALOG);
  // pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  // start I2S at 16 kHz with 16-bits per sample
  I2S.setAllPins(-1, 42, 41, -1, -1);
  if (!I2S.begin(PDM_MONO_MODE, 16000, 16)) {
    Serial.println("Failed to initialize I2S!");
    while (1)
      ;  // do nothing
  }
  uint8_t count = 0;
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    count++;
    if (count >= 75) {
      Serial.printf("\r\n-- wifi connect fail! --");
      break;
    }
    vTaskDelay(200);
  }
  Serial.printf("\r\n-- wifi connect success! --\r\n");
  Serial.println(WiFi.localIP());
  http.setTimeout(4000);
  http_client.setTimeout(4000);
  // gain_token();

  timer = timerBegin(0, 80, true);    //  80M的时钟 80分频 1M
  timerAlarmWrite(timer, 125, true);  //  1M  计125个数进中断  8K
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmEnable(timer);
  timerStop(timer);  //先暂停
}


uint32_t time1, time2;
void loop() {

  if (digitalRead(buttonPin) == 0)  //按键按下
  {
    if (digitalRead(buttonPin) == 0) {
      Serial.printf("Start recognition\r\n");
      digitalWrite(ledPin, HIGH);
      adc_start_flag = 1;
      timerStart(timer);

      // time1=micros();
      while (!adc_complete_flag)  //等待采集完成
      {
        ets_delay_us(10);
      }
      // time2=micros()-time1;

      timerStop(timer);
      adc_complete_flag = 0;  //清标志
      digitalWrite(ledPin, LOW);
      // memset(data_json, '\0', strlen(data_json));  //将数组清空
      memset(data_json, '\0', data_json_len * sizeof(char));
      strcat(data_json, "{");
      strcat(data_json, "\"format\":\"pcm\",");
      strcat(data_json, "\"rate\":16000,");
      strcat(data_json, "\"dev_pid\":1537,");
      strcat(data_json, "\"channel\":1,");
      strcat(data_json, "\"cuid\":\"Sbu0ZJFdgQePo3lQkhs3IpQDm2iAEa7j\",");
      strcat(data_json, "\"token\":\"");
      strcat(data_json, token.c_str());
      strcat(data_json, "\",");
      sprintf(data_json + strlen(data_json), "\"len\":%d,", adc_data_len * 2);
      strcat(data_json, "\"speech\":\"");
      strcat(data_json, base64::encode((uint8_t *)adc_data, adc_data_len * sizeof(uint16_t)).c_str());
      strcat(data_json, "\"");
      strcat(data_json, "}");
      // Serial.println(data_json);
      int httpCode;
      http_client.begin("http://vop.baidu.com/server_api");  //https://vop.baidu.com/pro_api
      http_client.addHeader("Content-Type", "application/json");
      httpCode = http_client.POST(data_json);

      if (httpCode == 200) {
        if (httpCode == HTTP_CODE_OK) {
          response = http_client.getString();
          http_client.end();
          Serial.print(response);
          // Parse JSON response
          // DynamicJsonDocument jsonDoc(512);
          deserializeJson(jsonDoc, response);
          String question = jsonDoc["result"][0];
          // 访问"result"数组，并获取其第一个元
          // 输出结果
          Serial.println("Input:" + question);
          answer = getGPTAnswer(question);
          // 切片句子输出，检测到。！？；：符号切割句子
          int index = 0;
          int len = answer.length();
          for (int i = 0; i < len; i++) {
            if (answer[i] == '。' || answer[i] == '！' || answer[i] == '？' || answer[i] == '，' || answer[i] == '：') {
              speech(answer.substring(index, i));
              index = i + 1;
            }
          }
          if (index < len) {
            speech(answer.substring(index, len));
          }

          // speech(answer);
          Serial.println("Answer: " + answer);
          // Serial.println("Enter a prompt:");

        } else {
          Serial.printf("[HTTP] GET... failed, error: %s\n", http_client.errorToString(httpCode).c_str());
          speech("语音识别在线故障，请检查api是否失效");
        }
      }
      // while (!digitalRead(buttonPin))
      //   ;
      Serial.println("Recognition complete\r\n");
    }
  }
  vTaskDelay(1);
}

